package io.github.yotta.logger.core.aop;

import com.alibaba.fastjson.JSON;
import io.github.yotta.logger.anno.annotation.context.BizIdLogContext;
import io.github.yotta.logger.anno.annotation.provider.BizIdFrom;
import io.github.yotta.logger.core.model.LogAnnoMetaData;
import io.github.yotta.logger.core.model.OpParam;
import io.github.yotta.logger.core.support.BizIdProviderDiscover;
import io.github.yotta.logger.core.support.LogContext;
import io.github.yotta.logger.core.support.LogMetaDataDiscover;
import io.github.yotta.logger.core.support.ParamResolver;
import io.github.yotta.logger.message.model.LogOperationDataMessage;
import io.github.yotta.logger.message.producer.LogDataProducer;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;

/**
 * @author yotta
 * @date 2023/3/21 9:56
 * 日志记录拦截器，完成日志记录
 */
@Slf4j
public class LoggerInterceptor implements MethodInterceptor {

    private final LogMetaDataDiscover logMetaDataDiscover;

    private final LogContext logContext;

    private final LogDataProducer logDataProducer;
    private final BizIdProviderDiscover bizIdProviderDiscover;


    public LoggerInterceptor(LogMetaDataDiscover logMetaDataDiscover,
                             LogContext logContext,
                             LogDataProducer logDataProducer,
                             BizIdProviderDiscover bizIdProviderDiscover) {
        this.logMetaDataDiscover = logMetaDataDiscover;
        this.logContext = logContext;
        this.logDataProducer = logDataProducer;
        this.bizIdProviderDiscover = bizIdProviderDiscover;
    }

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Method method = invocation.getMethod();
        LogAnnoMetaData logAnnoMetaData = logMetaDataDiscover.discover(method, method.getDeclaringClass());
        LogOperationDataMessage logMessage = startLog(invocation, logAnnoMetaData);
        try {
            Object result = invocation.proceed();
            afterLog(invocation, logAnnoMetaData, result, method, logMessage);
            return result;
        } catch (Throwable e) {
            if (logAnnoMetaData.isLogError()) {
                logMessage.setMessage(e.getMessage());
                logMessage.setIsSuccess(false);
            }
            throw e;
        } finally {
            BizIdLogContext.clear();
            if (Objects.nonNull(logMessage)) {
                this.logDataProducer.sendLogData(logMessage);
            }
        }
    }

    private void afterLog(MethodInvocation invocation,
                          LogAnnoMetaData logAnnoMetaData,
                          Object result,
                          Method method,
                          LogOperationDataMessage logMessage) {

        if (logAnnoMetaData.getBizIdFrom().equals(BizIdFrom.RETURN)) {
            Object bizId = bizIdProviderDiscover.getBizIdAfter(logAnnoMetaData, invocation.getArguments(), result, method);
            logMessage.setBizId(Objects.nonNull(bizId) ? bizId.toString() : null);
        }

        logMessage.setIsSuccess(true);
        if (logAnnoMetaData.isLogParam()) {
            logMessage.setResponseJson(Objects.nonNull(result) ? JSON.toJSONString(result) : null);
        }
    }

    protected LogOperationDataMessage startLog(MethodInvocation invocation, LogAnnoMetaData logAnnoMetaData) {
        LogOperationDataMessage logMessage = new LogOperationDataMessage();
        Method method = invocation.getMethod();
        if (logAnnoMetaData.getBizIdFrom().equals(BizIdFrom.PARAM)) {
            Object bizId = bizIdProviderDiscover.getBizIdBefore(logAnnoMetaData, invocation.getArguments(), method);
            logMessage.setBizId(Objects.nonNull(bizId) ? bizId.toString() : null);
        }
        logMessage.setApplication(logAnnoMetaData.getApplicationName());
        logMessage.setName(logAnnoMetaData.getName());
        logMessage.setModel(logAnnoMetaData.getModel());
        logMessage.setOccurTime(LocalDateTime.now());
        logMessage.setOperatorId(logContext.getOperatorId());
        logMessage.setOperatorName(logContext.getOperatorName());
        logMessage.setTenantId(logContext.getTenantId());
        try {
            if (logAnnoMetaData.isLogParam()) {
                List<OpParam> params = ParamResolver.resolveParam(invocation.getMethod(), invocation.getArguments());
                logMessage.setRequestJson(JSON.toJSONString(params));
            }
        } catch (Exception e) {
            log.error("parse method [{}#{}] param error by [{}] ", method.getDeclaringClass().getName(), method.getName(), e.getMessage(), e);
        }
        return logMessage;
    }


}
